/*
 *  Copyright (C) 2004 Cidero, Inc.
 *
 *  Permission is hereby granted to any person obtaining a copy of 
 *  this software to use, copy, modify, merge, publish, and distribute
 *  the software for any non-commercial purpose, subject to the
 *  following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 *  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY IN CONNECTION WITH THE SOFTWARE.
 * 
 *  File: $RCSfile: RendererStateModel.java,v $
 *
 */

package com.cidero.upnp;

import java.util.Observable;
import java.util.logging.Logger;

import com.cidero.upnp.AVTransport;
import com.cidero.util.TimeUtil;

/**
 *  Model for UPnP MediaRenderer state information. Used by various 
 *  UPnP controllers and bridges.
 *
 *  Notes:  notifyObservers() is not implicitly called when a single
 *  field is updated. It is left to the caller to invoke notifyObservers
 *  after all state changes have been made. This is intentional to 
 *  allow for multiple changes to be batched.
 */
public class RendererStateModel extends Observable implements Cloneable
{
  private static Logger logger = 
    Logger.getLogger("com.cidero.upnp");

  int     volume = 50;
  boolean mute = false;
  String  transportState = AVTransport.STATE_STOPPED;
  String  transportStatus = AVTransport.STATUS_OK;
  String  playMode = AVTransport.PLAY_MODE_NORMAL;
  
  String  trackNum = "0";
  String  trackTitle;
  String  trackArtist;
  String  trackRelTime = "00:00";
  String  trackDuration = "00:00";
  String  trackURI;
  String  trackMetaData;
  
  int     slideshowIntervalMs = 15000;
  
  public RendererStateModel()
  {
  }
  
  public void setVolume( String volume )
  {
    setVolume( Integer.parseInt( volume ) );
  }
  public void setVolume( int volume )
  {
    if( volume != this.volume )
    {
      this.volume = volume;
      setChanged();
    }
  }
  public int getVolume()
  {
    return volume;
  }
  public String getVolumeString()
  {
    return Integer.toString(volume);
  }

  public void setMute( String boolString )
  {
    if( boolString.equals("0") || boolString.equals("false") || 
        boolString.equals("off") )
      setMute( false );
    else
      setMute( true );
  }
  public void setMute( boolean mute )
  {
    if( mute != this.mute )
    {
      this.mute = mute;
      setChanged();
    }
  }
  public boolean getMute()
  {
    return mute;
  }
  public String getMuteString()
  {
    if( mute )
      return "1";
    else
      return "0";
  }
  
  /** 
   *  Set transport state, notifying all threads waiting on a change of
   *  state. Notify scheme is used when waiting for an audio renderer to
   *  finish playing a song, and we want to respond as quickly as possible to 
   *  the STOPPED state to start up the next song in the queue
   *
   *  @param  transportState
   *             Valid, supported values are AVTransport.STATE_STOPPED,
   *             AVTransport.STATE_PLAYING, AVTransport.STATE_PAUSED_PLAYBACK
   */
  public synchronized void setTransportState( String transportState )
  {
    if( ! transportState.equals( this.transportState ) )
    {
      this.transportState = transportState;
      setChanged();

      notifyAll();  // Notify threads waiting for change in transport state
    }
  }

  /** 
   *  Get the current transport state
   */
  public synchronized String getTransportState()
  {
    return transportState;
  }

  /** 
   * Wait for transport state to match a given value, or a timeout
   * 
   *  @param  transportState
   *             Valid, supported values are AVTransport.STATE_STOPPED,
   *             AVTransport.STATE_PLAYING, AVTransport.STATE_PAUSED_PLAYBACK
   */
  public synchronized boolean waitForTransportState( String reqTransportState, 
                                                     int timeoutMillisec )
  {
    if( transportState.equals( reqTransportState ) )
    {
      return true;
    }

    long startTime = System.currentTimeMillis();
    long elapsedTime = 0;
    
    while( elapsedTime < timeoutMillisec )
    {
      try
      {
        wait( timeoutMillisec-elapsedTime );
      }
      catch( InterruptedException e )
      {
        logger.warning("waitForTransportState: InterruptedException " + e  );
      }

      if( transportState.equals( reqTransportState ) )
        return true;
      
      elapsedTime = System.currentTimeMillis() - startTime;
    }

    return false;  // Timeout prior to matching state change
  }
  
  /** 
   *  Set transport status
   *
   *  @param  transportStatus
   *             Valid, supported values are AVTransport.STATE_STOPPED,
   *             AVTransport.STATE_PLAYING, AVTransport.STATE_PAUSED_PLAYBACK
   */
  public synchronized void setTransportStatus( String transportStatus )
  {
    if( ! transportStatus.equals( this.transportStatus ) )
    {
      this.transportStatus = transportStatus;
      setChanged();
    }
  }

  /** 
   *  Get the current transport state
   */
  public synchronized String getTransportStatus() {
    return transportStatus;
  }



  /** 
   *  Set/Get play mode
   *
   *  @param  playMode   Valid, supported values are:
   *                         AVTransport.NORMAL
   *                         AVTransport.SHUFFLE
   *                         AVTransport.REPEAT_ONE
   *                         AVTransport.REPEAT_ALL
   *                         AVTransport.RANDOM
   *                         AVTransport.DIRECT_1
   *                         AVTransport.INTRO
   */
  public void setPlayMode( String playMode )
  {
    if( ! playMode.equals( this.playMode ) )
    {
      this.playMode = playMode;
      setChanged();
    }
  }
  public String getPlayMode()
  {
    return playMode;
  }


  public void setTrackNum( String trackNum )
  {
    if( trackNum != this.trackNum )
    {
      this.trackNum = trackNum;
      setChanged();
    }
  }
  public String getTrackNum()
  {
    return trackNum;
  }

  public void setTrackDuration( String trackDuration )
  {
    if( trackDuration != this.trackDuration )
    {
      this.trackDuration = trackDuration;
      setChanged();
    }
  }
  public void setTrackDurationSec( int sec )
  {
    setTrackDuration( TimeUtil.toHHMMSS( sec ) );
  }
  public void setTrackDurationNoMillisec( String trackDuration )
  {
    int index = trackDuration.indexOf( "." );
    if( index > 0 )
      setTrackDuration( trackDuration.substring(0, index) );
    else
      setTrackDuration( trackDuration );
  }
  public String getTrackDuration()
  {
    return trackDuration;
  }

  public void setTrackRelTime( String trackRelTime )
  {
    if( trackRelTime != this.trackRelTime )
    {
      this.trackRelTime = trackRelTime;
      setChanged();
    }
  }
  public void setTrackRelTimeSec( int sec )
  {
    setTrackRelTime( TimeUtil.toHHMMSS( sec ) );
  }
  public String getTrackRelTime()
  {
    return trackRelTime;
  }

  public void setTrackURI( String trackURI )
  {
    if( trackURI != this.trackURI )
    {
      this.trackURI = trackURI;
      setChanged();
    }
  }
  public String getTrackURI()
  {
    return trackURI;
  }

  public void setTrackMetaData( String trackMetaData )
  {
    if( trackMetaData == null ) 
    {
      if( this.trackMetaData != null )
      {
        this.trackMetaData = trackMetaData;
        setChanged();
      }
    }
    else if( ! trackMetaData.equals( this.trackMetaData ) )
    {
      this.trackMetaData = trackMetaData;
      setChanged();
    }
  }
  public String getTrackMetaData()
  {
    return trackMetaData;
  }

  public void setTrackArtist( String trackArtist )
  {
    if( trackArtist != this.trackArtist )
    {
      this.trackArtist = trackArtist;
      setChanged();
    }
  }
  public String getTrackArtist()
  {
    return trackArtist;
  }

  public void setTrackTitle( String trackTitle )
  {
    if( trackTitle != this.trackTitle )
    {
      this.trackTitle = trackTitle;
      setChanged();
    }
  }
  public String getTrackTitle()
  {
    return trackTitle;
  }

  public void setSlideshowIntervalMs( int millis )
  {
    if( millis != this.slideshowIntervalMs )
    {
      this.slideshowIntervalMs = millis;
      setChanged();
    }
  }
  public int getSlideshowIntervalMs() {
    return slideshowIntervalMs;
  }
  

  /**
   *  Clone method. Class contains only simple fields for now, so default
   *  Cloneable method in base class Object is not overridden
   */
  public Object clone() throws CloneNotSupportedException
  {
    return super.clone();
  }

}
